package core.hmxchen.log.aspect;

import core.hmxchen.log.LogFormatter;
import core.hmxchen.log.LogFormatterFactory;
import core.hmxchen.log.annotation.LogPrint;
import core.hmxchen.log.config.LogPrintProperties;
import core.hmxchen.log.entity.MethodInfo;
import core.hmxchen.log.enums.Level;
import core.hmxchen.log.enums.Position;
import core.hmxchen.log.enums.Print;
import core.hmxchen.log.formatter.AfterReturningLogPrintFormatter;
import core.hmxchen.log.formatter.AfterThrowingLogPrintFormatter;
import core.hmxchen.log.formatter.AroundLogPrintFormatter;
import core.hmxchen.log.formatter.DoBeforeLogPrintFormatter;
import core.hmxchen.log.handler.LogHandler;
import core.hmxchen.log.handler.MethodHandler;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @program: log-print
 * @description: 日志打印切面
 * @author: hmxchen
 * @create: 2021-09-03 11:55
 **/
@Aspect
@Slf4j
public class LogPrintAspect {
    @Resource(type = LogPrintProperties.class)
    private LogPrintProperties logPrintProperties;
    /**
     * 以LogPrint注解的作为切入点
     */
    @Pointcut("@annotation(core.hmxchen.log.annotation.LogPrint)")
    public void logPrint() {
    }

    /**
     * 在切点之前织入
     *
     * @param joinPoint
     */
    @Before("logPrint()")
    public void doBefore(JoinPoint joinPoint) {
        if (LogHandler.isEnable(log)) {
            print((MethodSignature) joinPoint.getSignature(), Print.DO_BEFORE_LOG, joinPoint.getArgs(), null, null);
        }
    }

    /**
     * 返回参数时打印
     */
    @AfterReturning(value = "logPrint()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        if (LogHandler.isEnable(log)) {
            print((MethodSignature) joinPoint.getSignature(), Print.AFTER_RETURN_LOG, joinPoint.getArgs(), result, null);
        }
    }

    /**
     * 方法体内抛出异常打印
     */
    @AfterThrowing(value = "logPrint()", throwing = "throwable")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable throwable) {
        if (LogHandler.isEnable(log)) {
            print((MethodSignature) joinPoint.getSignature(), Print.AFTER_THROW_LOG, joinPoint.getArgs(), null, throwable);
        }
    }

    /**
     * 环绕
     *
     * @param proceedingJoinPoint
     * @return
     */
    @Around("logPrint()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null;
        Throwable throwable = null;
        Object[] args = proceedingJoinPoint.getArgs();
        long startTime = System.currentTimeMillis();
        try {
            result = proceedingJoinPoint.proceed();
        } catch (Throwable e) {
            throwable = e;
        }
        long time = System.currentTimeMillis() - startTime;
        if (LogHandler.isEnable(log)) {
            print((MethodSignature) proceedingJoinPoint.getSignature(), Print.AROUND_LOG, args, result + LogHandler.TIME_KEY + time, throwable);
        }
        if (throwable != null) {
            throw throwable;
        }
        return result;
    }

    private String getPrintDefaultImpl(Print print) {
        switch (print) {
            case DO_BEFORE_LOG:
                return logPrintProperties.getParamsLogImpl();
            case AFTER_RETURN_LOG:
                return logPrintProperties.getResultLogImpl();
            case AFTER_THROW_LOG:
                return logPrintProperties.getThrowLogImpl();
            case AROUND_LOG:
                return logPrintProperties.getAroundLogImpl();
        }
        return "";
    }
    private boolean hasRequest(MethodSignature signature){
        return signature.getMethod().isAnnotationPresent(RequestMapping.class)
        ||signature.getMethod().isAnnotationPresent(GetMapping.class)
                ||signature.getMethod().isAnnotationPresent(PostMapping.class)
                ||signature.getMethod().isAnnotationPresent(DeleteMapping.class)
                ||signature.getMethod().isAnnotationPresent(PutMapping.class);
    }
    public void print(MethodSignature signature, Print print, Object[] args, Object result, Throwable throwable) {
        if(logPrintProperties.isPrintRequest()&&hasRequest(signature)){
            //如果方法上还被请求注解注释并且开启了打印请求日志
            //将以请求日志打印为主
            return;
        }
        //获取目标方法方法的ParamLog注解
        LogPrint logPrint = signature.getMethod().getAnnotation(LogPrint.class);
        Print[] prints = logPrint.print();
        int index = Arrays.asList(prints).indexOf(print);
        if (index == -1) {
            return;
        }
        String logImplClass = "";
        String[] strings = logPrint.logImplClass();
        if (strings != null && strings.length > index) {
            logImplClass = strings[index];
        } else {
            logImplClass = getPrintDefaultImpl(print);
        }
        LogFormatter logFormatter = LogFormatterFactory.getClassName(logImplClass);
        Method method = signature.getMethod();
        String methodName = method.getName();
        MethodInfo methodInfo = null;
        Level level = logPrint.level();
        Position position = logPrint.position();
        if(Position.UNKNOWN.equals(position)){
            position = logPrintProperties.getPosition();
        }
        if(Level.UNKNOWN.equals(level)){
            level =Level.UNKNOWN.equals(logPrintProperties.getLevel())?Level.DEBUG:logPrintProperties.getLevel();
        }
        switch (position) {
            case ENABLED:
                methodInfo = MethodHandler.getMethodInfo(signature.getDeclaringTypeName(), methodName, signature.getParameterNames());
                break;
            default:
                methodInfo = MethodHandler.getMethodInfo(signature, MethodInfo.NATIVE_LINE_NUMBER);
        }
        String biz = logPrint.biz();
        //获取打印日志实现
        logFormatter.format(log, level, biz != "" ? biz : methodName, methodInfo,
                args, logPrint.paramFilter(), result, throwable);
    }
}
